/**
提取KAG脚本中的tag

调用方式：

三个参数，后两个参数可省略，省略则为false

extractTagsFromKS(ks文件名或脚本字符串,  [是否把第一个参数当作脚本字符串处理 = false], [是否把脚本当作宏定义的内容来解析 = false]);

例:

@loadplugin module=TagExtractor.dll
@iscript
var tagarray = extractTagsFromKS("test.ks");
@endscript


返回一个数组，数组中按顺序包含了脚本中各个tag的数据，每个tag是一个字典，tag名的key为“tagname”
在脚本中直接书写的文字解释为[_ch]，也就是说"tagname" => "_ch"，来和真正的ch相区别 连续的直接书写的文字会在一个_ch中输出
遇到label则解释为[_label]，包含两个参数为label和pagename，并包含一个cansave参数指示该label能否save，也就是是否包含"|"，包含的话，cansave为"true"，不包含则没有cansave参数，同时也没有pagename参数
遇到macro/endmacro解释为一个[macro]，参数为macroscenario
遇到macro定义内部的tag的*参数将以"_allparams" => "true"的形式装入，只在解析宏定义的时候出现
遇到iscript/endscript解释为一个[iscript]，参数为tjs
遇到注释解释为[_remark] 参数为text
遇到换行装入一个[_cr linenum=换行后的行号]
遇到语法错误则装入一个[_syntaxerror]并立即返回，包含error(错误标识)和linenum(行号)两个成员 如果返回结果的最后一个tag是_SyntaxError，就说明那个地方遇到了语法错误
遇到类似xxx=&"f.foo"这种参数，会把&"f.foo"都当作参数的值，包括引号
注意，iscript/endscript必须单独占一行，否则会出问题
该插件无法处理脚本中的行末\(右斜杠)，且不支持IgnoreCR=false模式

*/

//---------------------------------------------------------------------------
#include <windows.h>
#include "tp_stub.h"
#include <string>

//---------------------------------------------------------------------------

class TagExtractor
{
public:
    TagExtractor(void) : filebuf(NULL), ArrayObj(NULL), DicObj(NULL), Macros(NULL), DicClear(NULL), DicAssign(NULL) {}
    void loadScenario(const ttstr& filename, bool isstring = false, bool ismacro = false);
    bool gotoNextLine(bool addcr = true); // 没有下一行则返回false
    bool trimStart(bool addcr = true);
    void trimback(tjs_char* p);// 从p开始反向trim
    void release(void);
    void getLine(void);
    int parseRemark(void); // 开头有检测，如果不是注释就不处理，不处理返回1，处理返回2，有错误返回0，下同
    int parseLabel(void); // 开头有检测，如果不是label就不处理
    int parseISctipt(void); // 开头有检测，如果不是isctipt就不处理，有语法错误就返回false
    int parseMacro(void); // 开头有检测，如果不是Macro就不处理，有语法错误就返回false
    bool getNextTag(void);
    bool extractTags(void);
    tTJSVariant getTagArray(void);
private:
    tjs_int strequal(const tjs_char* pS, tjs_int lenS, const tjs_char* pD)
    {
        return ttstr(pS, lenS).CompareIC(pD);
    }
    void cleaeDic()
    {
        DicClear->FuncCall(0, NULL, NULL, NULL, 0, NULL, DicObj);
    }

    void setValueToDic(tjs_char* key, tjs_int keylen, tjs_char* value, tjs_int valuelen)
    {
        setValueToDic(ttstr(key, keylen), value, valuelen, false);
    }

    void setValueToDic(const ttstr& key, tjs_char* value, tjs_int valuelen, bool isVS)
    {
        if(valuelen > 0)
            setValueToDic(key, ttstr(value, valuelen), isVS);
        else
            setValueToDic(key, ttstr(value), isVS);

    }

    void setValueToDic(const ttstr& key, const ttstr& value, bool isVS)
    {
        ValueVariant = value;
        if(isVS)
            DicObj->PropSetByVS(TJS_MEMBERENSURE, key.AsVariantStringNoAddRef(), &ValueVariant, DicObj);
        else
            DicObj->PropSet(TJS_MEMBERENSURE, key.c_str(), NULL, &ValueVariant, DicObj);
    }

    void addDicToArr()
    {
        tTJSVariant DicVar(DicObj);
        tTJSVariant *psrc = &DicVar;
        iTJSDispatch2* tempdicobj = TJSCreateDictionaryObject();
        DicAssign->FuncCall(0, NULL, NULL, NULL, 1, &psrc, tempdicobj);
        tTJSVariant tempdicvar(tempdicobj, tempdicobj);
        ArrayObj->PropSetByNum(0, tagCount++, &tempdicvar, ArrayObj);
        tempdicobj->Release();
    }

    int addSyntaxErrorToArr(const ttstr& errmsg)
    {
        cleaeDic();
        setValueToDic(__tag_name, _SyntaxError_name, true);
        setValueToDic(TJS_W("error"), errmsg, false);
        setValueToDic(TJS_W("linenum"), lineCount, false);
        addDicToArr();

        return 0; // 为了调用时少写一行代码
    }

    void addCrToArr()
    {
        cleaeDic();
        setValueToDic(__tag_name, _cr_name, true);
        setValueToDic(TJS_W("linenum"), lineCount, false);
        addDicToArr();
    }

    ttstr __tag_name;
	ttstr __eol_name;
	ttstr __storage_name;
	ttstr __target_name;
	ttstr __exp_name;
	ttstr __text_name;

    iTJSDispatch2* ArrayObj;
    iTJSDispatch2* DicObj;
	iTJSDispatch2* Macros;
	iTJSDispatch2* DicClear;
	iTJSDispatch2* DicAssign;

    tjs_char* filebuf;
    ttstr tmp;
    ttstr normalch;
    tjs_int tagCount;
    tjs_int lineCount;

    tTJSVariant ValueVariant;
    tjs_char ldelim; // last delimiter
    tjs_char* CurLineStr;
    bool RecordingMacro;






    tTJSVariant _remark_name;
    tTJSVariant _label_name;
    tTJSVariant _SyntaxError_name;
    tTJSVariant _iscript_name;
    tTJSVariant _macro_name;
    tTJSVariant _ch_name;
    tTJSVariant _cr_name;


};

static bool inline TVPIsWS(tjs_char ch)
{
	// is white space ?
	return (ch == TJS_W(' ') || ch == TJS_W('\t'));
}

void TagExtractor::loadScenario(const ttstr& filename, bool isstring, bool ismacro)
{
    __tag_name = ttstr(TJSMapGlobalStringMap(TJS_W("tagname")));
    __eol_name = ttstr(TJSMapGlobalStringMap(TJS_W("eol")));
	__storage_name = ttstr(TJSMapGlobalStringMap(TJS_W("storage")));
	__target_name = ttstr(TJSMapGlobalStringMap(TJS_W("target")));
	__exp_name = ttstr(TJSMapGlobalStringMap(TJS_W("exp")));
	__text_name = ttstr(TJSMapGlobalStringMap(TJS_W("text")));

	_remark_name = TJS_W("_remark");
	_label_name = TJS_W("_label");
	_SyntaxError_name = TJS_W("_syntaxerror");
	_iscript_name = TJS_W("iscript");
	_macro_name = TJS_W("macro");
	_ch_name = TJS_W("_ch");
	_cr_name = TJS_W("_cr");

	iTJSDispatch2* dictclass;
	ArrayObj = TJSCreateArrayObject();
	DicObj = TJSCreateDictionaryObject(&dictclass);
	Macros = TJSCreateDictionaryObject();
	try
	{
		// retrieve clear method from dictclass
		tTJSVariant val;
		tjs_error er;

		er = dictclass->PropGet(0, TJS_W("clear"), NULL, &val, dictclass);
		DicClear = val.AsObject();

		er = dictclass->PropGet(0, TJS_W("assign"), NULL, &val, dictclass);
		DicAssign = val.AsObject();
	}
	catch(...)
	{
	    ArrayObj->Release();
		dictclass->Release();
		DicObj->Release();
		Macros->Release();
		if(DicClear) DicClear->Release();
		if(DicAssign) DicAssign->Release();
		throw;
	}
	dictclass->Release();

    // 获取脚本字符串指针
    if(isstring)
	{
	    tmp = filename;
	}
	else
	{
		iTJSTextReadStream * stream = NULL;
		try
		{
			stream = TVPCreateTextStreamForRead(filename, TJS_W(""));
			stream->Read(tmp, 0);
		}
		catch(...)
		{
			if(stream) stream->Destruct();
			throw;
		}
		if(stream) stream->Destruct();
	}

	filebuf = new tjs_char[tmp.length() + 1];
	TJS_strcpy(filebuf, tmp.c_str());
	filebuf[tmp.length()] = TJS_W('\0');
	CurLineStr = filebuf;
	RecordingMacro = ismacro;
    tagCount = 0;
    lineCount = 1;
}

bool TagExtractor::gotoNextLine(bool addcr)
{
    while(*CurLineStr != TJS_W('\n') && *CurLineStr != TJS_W('\0'))
        CurLineStr++;
    if(*CurLineStr == TJS_W('\0'))
        return false;
    CurLineStr++;
    lineCount++;
    if(addcr)
        addCrToArr();
    return true;
}

bool TagExtractor::trimStart(bool addcr)
{
    while(*CurLineStr == TJS_W('\t') || *CurLineStr == TJS_W('\r') || *CurLineStr == TJS_W('\n'))
    {
        if(*CurLineStr == TJS_W('\n'))
        {
            lineCount++;
            if(addcr)
                addCrToArr();
        }
        CurLineStr++;
    }
    if(*CurLineStr == TJS_W('\0'))
        return false;
    return true;
}

void TagExtractor::trimback(tjs_char* p)
{
    while(*p == TJS_W('\t') || *p == TJS_W('\r') || *p == TJS_W('\n'))
    {
        *p = TJS_W('\0');
        p--;
    }
}

void TagExtractor::release()
{
    if(ArrayObj) ArrayObj->Release();
    if(DicObj) DicObj->Release();
    if(Macros) Macros->Release();
    if(DicClear) DicClear->Release();
    if(DicAssign) DicAssign->Release();

    if(filebuf != NULL) delete[] filebuf;
}

int TagExtractor::parseRemark()
{
    if(*CurLineStr == TJS_W(';'))
    {
        tjs_char * remarkstart = CurLineStr;

        gotoNextLine(false);
        trimback(CurLineStr - 1);

        cleaeDic();
        setValueToDic(__tag_name, _remark_name, true);
        setValueToDic(__text_name, remarkstart, -1, true);
        addDicToArr();

        addCrToArr();
        trimStart();

        return 2;
    }
    return 1;
}

int TagExtractor::parseLabel()
{
    if(*CurLineStr == TJS_W('*'))
    {
        tjs_char * labelstart = CurLineStr;

        gotoNextLine(false);
        trimback(CurLineStr - 1);

        cleaeDic();
        setValueToDic(__tag_name, _label_name, true);

        tjs_char * vl = labelstart;
        while(*vl != TJS_W('|') && *vl != TJS_W('\0'))
            vl++;

        if(*vl == TJS_W('|'))
        {
            setValueToDic(TJS_W("label"), ttstr(labelstart, vl-labelstart), false);
            setValueToDic(TJS_W("pagename"), ttstr(vl + 1), false);
            setValueToDic(TJS_W("cansave"), ttstr(TJS_W("true")), false);
        }
        else
        {
            setValueToDic(TJS_W("label"), ttstr(labelstart), false);
        }
        addDicToArr();

        addCrToArr();
        trimStart();

        return 2;
    }
    return 1;
}

int TagExtractor::parseISctipt()
{
    tjs_char * p = CurLineStr;
    if(p[0] == TJS_W('[') && !strequal(p, 9, TJS_W("[iscript]"))
        || p[0] == TJS_W('@') && !strequal(p, 8, TJS_W("@iscript")) )
    {
			// inline TJS script
 			if(RecordingMacro)
 			    return addSyntaxErrorToArr(TJS_W("TVPLabelOrScriptInMacro"));

            bool isNotLastLine = gotoNextLine(false);
            if(isNotLastLine)
            {
                tjs_char * pSctiptStart = CurLineStr;
                tjs_char * pSctiptend;

                while(isNotLastLine)
                {
                    pSctiptend = CurLineStr;

                    if(!trimStart(false))
                        break;
                    p = CurLineStr;

                    if(p[0] == TJS_W('[') && !strequal(p, 11, TJS_W("[endscript]"))
                        || p[0] == TJS_W('@') && !strequal(p, 10, TJS_W("@endscript")) )
                    {
                        trimback(pSctiptend - 1);

                        cleaeDic();
                        setValueToDic(__tag_name, _iscript_name, true);
                        setValueToDic(TJS_W("tjs"), pSctiptStart, pSctiptend - pSctiptStart, false);
                        addDicToArr();

                        gotoNextLine();
                        trimStart();
                        return 2;
                    }


                    isNotLastLine = gotoNextLine(false);
                }
            }

            return addSyntaxErrorToArr(TJS_W("TVPKAGInlineScriptNotEnd"));
    }
    trimStart();
    return 1;
}

int TagExtractor::parseMacro()
{
    tjs_char * p = CurLineStr;
    if(p[0] == TJS_W('[') && !strequal(p, 7, TJS_W("[macro]"))
        || p[0] == TJS_W('@') && !strequal(p, 6, TJS_W("@macro")) )
    {
        if(p[0] == TJS_W('['))
            CurLineStr += 7;
        else
            CurLineStr += 6;

            bool isNotLastLine = trimStart(false);
            if(isNotLastLine)
            {
                tjs_char * pMacroStart = CurLineStr;
                tjs_char * pMacroend;

                while(isNotLastLine)
                {

                    if(!trimStart(false))
                        break;
                    pMacroend = CurLineStr;
                    p = CurLineStr;

                    if(p[0] == TJS_W('[') && !strequal(p, 10, TJS_W("[endmacro]"))
                        || p[0] == TJS_W('@') && !strequal(p, 9, TJS_W("@endmacro")) )
                    {
                        trimback(pMacroend - 1);

                        cleaeDic();
                        setValueToDic(__tag_name, _macro_name, true);
                        setValueToDic(TJS_W("macroscenario"), pMacroStart, pMacroend - pMacroStart, false);
                        addDicToArr();

                        if(p[0] == TJS_W('['))
                            CurLineStr = p + 10;
                        else
                            CurLineStr = p + 9;
                        trimStart();
                        return 2;
                    }


                    isNotLastLine = gotoNextLine(false);
                }
            }

            return addSyntaxErrorToArr(TJS_W("TVPKAGMacroNotEnd"));
    }
    trimStart();
    return 1;
}

bool TagExtractor::getNextTag()
{
    if(*CurLineStr == TJS_W('\0'))
        return false;

    if(*CurLineStr == TJS_W('\n'))
    {
        gotoNextLine();
        trimStart();
        return true;
    }

    tjs_char* p = CurLineStr;
    tjs_char* tagstart = CurLineStr;

    if(*p == TJS_W('@'))
    {
        // line command mode
        ldelim = 0; // tag last delimiter is a null terminater
        gotoNextLine(false);
        trimback(CurLineStr - 1);
        trimStart();
    }
    else
    {
        if(*p != TJS_W('[') ||
            *p == TJS_W('[') &&
            p[1] == TJS_W('['))
        {
            // normal character
            normalch.Clear();
            while(*p != TJS_W('\t') && *p != TJS_W('\r') && *p != TJS_W('\n') && (*p != TJS_W('[') || *p == TJS_W('[') && p[1] == TJS_W('[')))
            {
                normalch += *p;
                if(*p == TJS_W('['))
                    p++;
                p++;
            }

            cleaeDic();
            setValueToDic(__tag_name, _ch_name, true);
            setValueToDic(__text_name, normalch, true);
            addDicToArr();

            CurLineStr = p;

            trimStart();
            return true;
        }

        ldelim = TJS_W(']');
    }

    // a tag
    cleaeDic();
    p++;
    while(TVPIsWS(*p))
        p++;
    if(*p == TJS_W('\0'))
        return addSyntaxErrorToArr(TJS_W("TVPKAGSyntaxError"));

    // tag name
    const tjs_char * tagnamestart = p;
    while(*p != TJS_W('\0') && !TVPIsWS(*p) && *p != ldelim)
        p++;

    if(tagnamestart == p)
        return addSyntaxErrorToArr(TJS_W("TVPKAGSyntaxError"));

    ttstr tagname(tagnamestart, p - tagnamestart);
    tagname.ToLowerCase();
    setValueToDic(__tag_name, tagname, true);

    // tag attributes
    while(true)
    {
        while(TVPIsWS(*p))
            p++;

        if(*p == ldelim)
        {
            // tag ended
            addDicToArr();
            if(CurLineStr <= p)
            {
                if(*p != TJS_W('\0'))
                    CurLineStr = p + 1;
                else
                    CurLineStr = p;
            }
            else
                addCrToArr();
            trimStart();
            return true;
        }

        if(*p == TJS_W('\0'))
            return addSyntaxErrorToArr(TJS_W("TVPKAGSyntaxError"));

        // attrib name
        if(*p == TJS_W('*'))
        {
            // macro entity all
            setValueToDic(TJS_W("_allparams"), TJS_W("true"), false);
            p++;
            continue;
        }

        const tjs_char *attribnamestart = p;
        while(*p != TJS_W('\0') && !TVPIsWS(*p) && *p != ldelim && *p != TJS_W('='))
            p++;

        const tjs_char *attribnameend = p;

        ttstr attribname(attribnamestart, attribnameend - attribnamestart);
        attribname.ToLowerCase();

        // =
        while(TVPIsWS(*p))
            p++;

        bool entity = false;
        bool macroarg = false;
        ttstr value;

        if(*p != TJS_W('='))
        {
            // arrtibute value omitted
            value = TJS_W("true"); // always true
        }
        else
        {
            p++;
            while(TVPIsWS(*p))
                p++;
            if(*p == TJS_W('\0'))
                return addSyntaxErrorToArr(TJS_W("TVPKAGSyntaxError"));

            // attrib value
            tjs_char vdelim = 0; // value delimiter


            if(*p == TJS_W('\"') ||
                *p == TJS_W('\''))
            {
                vdelim = *p;
                p++;
            }

            const tjs_char *valuestart = p;

            bool valueStartByAnd = false;

            if(*p == TJS_W('&')) // 为了对付storage=&"f.xx" 这种参数
            {
                p++;
                valueStartByAnd = true;

                if(*p == TJS_W('\"') ||
                    *p == TJS_W('\''))
                {
                    vdelim = *p;
                    p++;
                }
            }

            while(*p != TJS_W('\0') &&
                (vdelim ? (*p != vdelim) :
                    (*p != ldelim && !TVPIsWS(*p)) ) )
            {
                if(*p == TJS_W('`'))
                {
                    // escaped with '`'
                    p++;
                    if(*p == TJS_W('\0'))
                        return addSyntaxErrorToArr(TJS_W("TVPKAGSyntaxError"));
                }
                p++;
            }

            if(ldelim != 0 && *p == TJS_W('\0'))
                return addSyntaxErrorToArr(TJS_W("TVPKAGSyntaxError"));

            if(vdelim && valueStartByAnd) // 如果参数是类似&"f.xx"这种，就把那引号也算进去
                p++;

            const tjs_char *valueend = p;

            if(vdelim && !valueStartByAnd) // 只有引号没有&，就不用把引号算计进去了，而是要跳过引号
                p++;

            // unescape ` character of value
            value = ttstr(valuestart, valueend - valuestart);
            if(valueend != valuestart)
            {
                // value has at least one character
                tjs_char * vp = value.Independ();
                tjs_char * wvp = vp;

                while(*vp)
                {
                    if(*vp == TJS_W('`'))
                    {
                        vp++;
                        if(!*vp) break;
                    }
                    *wvp = *vp;
                    vp++;
                    wvp++;
                }
                *wvp = 0;
                value.FixLen();
            }
        }

        // store value into the dictionary object
        ValueVariant = value;
        setValueToDic(attribname, ValueVariant, false);
    }
}

bool TagExtractor::extractTags()
{
    int result;
    while(true)
    {
        if(!trimStart())
            break;

        result = parseRemark();
        if(result == 0)
            break;
        else if(result == 2)
            continue;

        result = parseLabel();
        if(result == 0)
            break;
        else if(result == 2)
            continue;

        result = parseISctipt();
        if(result == 0)
            break;
        else if(result == 2)
            continue;

        result = parseMacro();
        if(result == 0)
            break;
        else if(result == 2)
            continue;

        if(!getNextTag()) break;
    }
}

tTJSVariant TagExtractor::getTagArray(void)
{
    tTJSVariant result(ArrayObj, ArrayObj);
    return result;
}






//---------------------------------------------------------------------------
// 解析KAG脚本
//---------------------------------------------------------------------------
class tTJSExtractTagsFromKSFunction : public tTJSDispatch
{
	tjs_error TJS_INTF_METHOD FuncCall(
		tjs_uint32 flag, const tjs_char * membername, tjs_uint32 *hint,
		tTJSVariant *result,
		tjs_int numparams, tTJSVariant **param, iTJSDispatch2 *objthis);
} * TJSExtractTagsFromKSFunction;

tjs_error TJS_INTF_METHOD tTJSExtractTagsFromKSFunction::FuncCall(
	tjs_uint32 flag, const tjs_char * membername, tjs_uint32 *hint,
	tTJSVariant *result,
	tjs_int numparams, tTJSVariant **param, iTJSDispatch2 *objthis)
{
	if(numparams < 1) return TJS_E_BADPARAMCOUNT;

	TagExtractor tagextractor;

	bool isstring = false;
	bool ismacro = false;
	if(numparams >= 2)
        isstring = (*param[1]).AsInteger();
    if(numparams >= 3)
        ismacro = (*param[2]).AsInteger();

	tagextractor.loadScenario(*param[0], isstring, ismacro);

	tagextractor.extractTags();

	if(result)
	{
		*result = tagextractor.getTagArray();
	}

	return TJS_S_OK;
}
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason,
	void* lpReserved)
{
	return 1;
}
//---------------------------------------------------------------------------
static tjs_int GlobalRefCountAtInit = 0;
extern "C" HRESULT _stdcall _export V2Link(iTVPFunctionExporter *exporter)
{
	// スタブの初期化(必ず記述する)
	TVPInitImportStub(exporter);

	// AverageFunction の作成と登録
	tTJSVariant val;

	// TJS のグローバルオブジェクトを取得する
	iTJSDispatch2 * global = TVPGetScriptDispatch();



	//-----------------------------------------------------------------------
	// 1 まずオブジェクトを作成
	TJSExtractTagsFromKSFunction = new tTJSExtractTagsFromKSFunction();

	// 2 AverageFunction を tTJSVariant 型に変換
	val = tTJSVariant(TJSExtractTagsFromKSFunction);

	// 3 すでに val が AverageFunction を保持しているので、AverageFunction は
	//   Release する
	TJSExtractTagsFromKSFunction->Release();


	// 4 global の PropSet メソッドを用い、オブジェクトを登録する
	global->PropSet(
		TJS_MEMBERENSURE, // メンバがなかった場合には作成するようにするフラグ
		TJS_W("extractTagsFromKS"), // メンバ名 ( かならず TJS_W( ) で囲む )
		NULL, // ヒント ( 本来はメンバ名のハッシュ値だが、NULL でもよい )
		&val, // 登録する値
		global // コンテキスト ( global でよい )
		);
	//-----------------------------------------------------------------------

	// - global を Release する
	global->Release();

	// もし、登録する関数が複数ある場合は 1 ～ 4 を繰り返す


	// val をクリアする。
	// これは必ず行う。そうしないと val が保持しているオブジェクト
	// が Release されず、次に使う TVPPluginGlobalRefCount が正確にならない。
	val.Clear();


	// この時点での TVPPluginGlobalRefCount の値を
	GlobalRefCountAtInit = TVPPluginGlobalRefCount;
	// として控えておく。TVPPluginGlobalRefCount はこのプラグイン内で
	// 管理されている tTJSDispatch 派生オブジェクトの参照カウンタの総計で、
	// 解放時にはこれと同じか、これよりも少なくなってないとならない。
	// そうなってなければ、どこか別のところで関数などが参照されていて、
	// プラグインは解放できないと言うことになる。

	return S_OK;
}
//---------------------------------------------------------------------------
extern "C" HRESULT _stdcall _export V2Unlink()
{
	// 吉里吉里側から、プラグインを解放しようとするときに呼ばれる関数。

	// もし何らかの条件でプラグインを解放できない場合は
	// この時点で E_FAIL を返すようにする。
	// ここでは、TVPPluginGlobalRefCount が GlobalRefCountAtInit よりも
	// 大きくなっていれば失敗ということにする。
	if(TVPPluginGlobalRefCount > GlobalRefCountAtInit) return E_FAIL;
		// E_FAIL が帰ると、Plugins.unlink メソッドは偽を返す

	// TJS のグローバルオブジェクトに登録した average 関数などを削除する

	// - まず、TJS のグローバルオブジェクトを取得する
	iTJSDispatch2 * global = TVPGetScriptDispatch();

	// - global の DeleteMember メソッドを用い、オブジェクトを削除する
	if(global)
	{
		// TJS 自体が既に解放されていたときなどは
		// global は NULL になり得るので global が NULL でない
		// ことをチェックする

		global->DeleteMember(
			0, // フラグ ( 0 でよい )
			TJS_W("extractTagsFromKS"), // メンバ名
			NULL, // ヒント
			global // コンテキスト
			);
			// 登録した関数が複数ある場合は これを繰り返す
	}

	// - global を Release する
	if(global) global->Release();

	// スタブの使用終了(必ず記述する)
	TVPUninitImportStub();

	return S_OK;
}
//---------------------------------------------------------------------------
static tjs_char option_message[] =
L"テストカテゴリ:テスト設定項目1;これはテスト用のオプション設定項目です。|"
L"testoption1|select,*default;デフォルトの値,1;選択可能な値1,2;選択可能な値2\n"
L"テストカテゴリ:テスト設定項目2;これはテスト用のオプション設定項目です。|"
L"testoption2|string(64),*デフォルトの文字列\n";
//---------------------------------------------------------------------------
extern "C" const wchar_t* _stdcall _export GetOptionDesc()
{
	// GetOptionDesc は、吉里吉里設定ツールに渡すオプションの情報を返すための
	// 関数。
	// 一行につき一つのオプションに関する情報を書く。
	// 書式は
	// カテゴリ:設定名;説明|オプション名|オプションとして設定可能な値
	// (区切り記号に注意)
	// 「オプション名」には '-' (ハイフン) はつけない。
	// 「オプションとして設定可能な値」は以下の書式である。
	// タイプ,値の値;簡単な説明[,値;値の簡単な説明 ...]
	// 「タイプ」は現バージョンでは select と string のみ使用可能。

	// 「タイプ」に select を指定すると、コンボボックスから値を選択することが
	// できるようになる。
	// 「値;値の簡単な説明」の先頭に * をつけるとそれがデフォルトのオプション
	// であることを示す。デフォルトのオプションは必ず一つは無くてはならない。
	// 「値」は実際に吉里吉里本体へのオプションとして渡されるものを、
	// 「値の簡単な説明」は人間が読んで理解できるものを書く。

	// 「タイプ」に string を指定すると、エディットボックスでユーザが任意の文
	// 字列を入力できるようになる。
	// 「タイプ」に string を指定する場合は、string(n),*デフォルト文字列 の
	// 形で n には最大文字数を記述する。0 で文字数制限はなくなるが、できれば
	// 制限をつけたほうがよい。
	// 「デフォルト文字列」にはデフォルトの文字列を記述する。string(n) のあと
	// の , (カンマ) も * (アステリスク) も省略できない。

	// 例は /msg/win32/option_desc_ja.txt にもある。


	return option_message;

	// 各オプション末や、最後の改行記号を忘れると大変なことになる。


	// 吉里吉里設定などは、そのプラグインに設定可能な項目があるかどうかを
	// プラグインDLLの
	// 先頭2KB以内に "(--has-option--)" という文字列があるかどうか
	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// (２重引用符は含めず)
	// で判断するため、先頭2KB以内にこれを書かなければならない。
	// bcc の場合はリンカオプション /GC で /GC"(--has-option--)"
	// と記述し、VC++ の場合もリンカオプション /COMMENT で
	// /COMMENT:"(--has-option--)" と記述する。
	// BCB の IDE からは プロジェクトオプションの「リンカ(詳細)」の
	// 「イメージのコメント」に "(--has-option--)" と書く。
	// (２重引用符で囲まないとエラーになるかもしれない)

	// UPX などで圧縮するとここの部分が消されてしまうので、
	// ヘッダの未使用領域 (0x80～0x1ff の 00 で埋まっている所など) に
	// 無理矢理書くなどする。

	// 本体に渡されたオプションは TVPGetCommandLine 関数で取得できる。
	// tTJSVariant val;
	// if(TVPGetCommandLine(TJS_W("-testoption1"), &val))
	// {
	//      // ここに オプション "-testoption1" が渡されていたときの
	//      // 処理を書く。val にはその値が入っている。
	//      // /environ/win32/wuvorbis/WuVorbisMainUnit.cpp 等参照
	// }
	// TVPGetcommandLine の実行は現バージョンでは高価なのでできれば
	// この関数の呼び出しの結果はキャッシュした方がよい。

	// 吉里吉里設定は、プラグインを「吉里吉里本体と同じフォルダ」と
	// 「吉里吉里本体のと同じフォルダにある plugin というフォルダ」から
	// しか検索しない。従って設定可能なオプションを含むプラグインは
	// Releaser などでアーカイブ中に含めてはならない(もともとReleaserで
	// プラグインを中に含めることは推奨されていない)。

	// この GetOptionDesc 関数内では tp_stub に記述されているような
	// 各吉里吉里本体内の関数は利用できない。
}
//---------------------------------------------------------------------------
/*
	V2Link と V2Unlink は DLL からエクスポートされている必要がある。
	従って、.def ファイルを作成し

EXPORTS
	V2Link
	V2Unlink

	と記述してプロジェクトに追加する。

	GetOptionsDesc を使う場合はそれも追加する。
*/
//---------------------------------------------------------------------------

